package tr.edu.yildiz.ce.dap.appagentinfra.service;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Iterator;
import java.util.Set;

import tr.edu.yildiz.ce.dap.appagent.exception.AgentCommunicationException;
import tr.edu.yildiz.ce.dap.appagent.service.MessageConverter;
import tr.edu.yildiz.ce.dap.appagent.service.infra.NetworkManager;
import tr.edu.yildiz.ce.dap.appagent.util.AgentProperties;

public class NetworkManagerImpl implements NetworkManager{
	
	Charset charset = Charset.forName(AgentProperties.getInstance().getENCODING());
    CharsetDecoder decoder = charset.newDecoder();

	public String joinMulticast(String multicastAddress,int port) throws AgentCommunicationException{
		String s = null;
		InetAddress ia = null;
	    byte[] buffer = new byte[65535];
	    DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
	    try{
	    	ia = InetAddress.getByName(multicastAddress);
	        MulticastSocket ms = new MulticastSocket(port);
	        ms.setSoTimeout(5000);
	        ms.joinGroup(ia);
	        while (true) {
	            ms.receive(dp);
	            s = new String(dp.getData(),0,dp.getLength());
	            if (s != null && s.length() > 0)
	            	break;
	        }
			ms.leaveGroup(ia);
			ms.close();
	    }catch(UnknownHostException ue){
	    	throw new AgentCommunicationException(ue);	
	    }catch (IOException ie) {
	    	throw new AgentCommunicationException(ie);
	    }
		return s;
	}
	
	public String sendMessage(String message,String address,int port) throws AgentCommunicationException{
		InetSocketAddress ISA = null;
		SocketChannel clientNIO = null;
		Selector selector = null;
		SelectionKey key = null;

		try {
			clientNIO = SocketChannel.open();
			clientNIO.configureBlocking(false);
			InetAddress addr = InetAddress.getByName(address);
			ISA = new InetSocketAddress(addr, port);
		} catch (IOException e) {
			throw new AgentCommunicationException(e);
		}


		try {
			clientNIO.connect(ISA);
		} catch (IOException e) {
			throw new AgentCommunicationException("Host with address:"
					+ address + " can not be found.");
		}

		try {
			selector = Selector.open();
			SelectionKey clientKey = clientNIO.register(selector,
					SelectionKey.OP_CONNECT);
			while (selector.select(1000) > 0) {
				Set keys = selector.selectedKeys();
				Iterator iter = keys.iterator();
				while (iter.hasNext()) {
					key = (SelectionKey) iter.next();
					iter.remove();
					SocketChannel channel = (SocketChannel) key.channel();
					if ((key.isValid()) && (key.isConnectable())) {
						if (channel.isConnectionPending())
							channel.finishConnect();
						ByteBuffer serverBuf = null;
						serverBuf = ByteBuffer.wrap(message.getBytes());
						channel.write(serverBuf);
						serverBuf.clear();

						CharBuffer response = null;
						do {
							ByteBuffer clientBuf = ByteBuffer.allocateDirect(4096);
							clientBuf.clear();
							channel.read(clientBuf);
							clientBuf.flip();
							Charset charset = Charset
									.forName(AgentProperties.getInstance().getENCODING());
							CharsetDecoder decoder = charset.newDecoder();
							response = decoder.decode(clientBuf);
							clientBuf.clear();
						} while (response.toString().length()== 0);
						return response.toString();
					}
				}
			}
		} catch (SocketException e) {
			try {
				key.channel().close();
				key.cancel();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
			throw new AgentCommunicationException(e);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public void listen(MessageReceivedCallBack mrCallBack, int port) {
		try {
			ByteBuffer buffer = ByteBuffer.allocate(2048);
		    Selector selector = Selector.open();

		    ServerSocketChannel server = ServerSocketChannel.open();
		    server.socket().bind(new java.net.InetSocketAddress(port));
		    server.configureBlocking(false);
		    SelectionKey serverkey = server.register(selector, SelectionKey.OP_ACCEPT);
		    
			for (;;) {
				if (mrCallBack.isStop()){
					server.socket().close();
					serverkey.cancel();
				}
				selector.select();
				Set keys = selector.selectedKeys();
				for (Iterator i = keys.iterator(); i.hasNext();) {
					SelectionKey key = (SelectionKey) i.next();
					i.remove();

					if (key == serverkey) {
						if (key.isAcceptable()) {
							SocketChannel client = server.accept();
							client.configureBlocking(false);
							SelectionKey clientkey = client.register(selector,
									SelectionKey.OP_READ);
							clientkey.attach(new Integer(0));
						}
					} else {
						SocketChannel client = (SocketChannel) key.channel();
						if (!key.isReadable())
							continue;
						int bytesread = client.read(buffer);
						if (bytesread == -1) {
							key.cancel();
							client.close();
							continue;
						}
						buffer.flip();
						String msg = decoder.decode(buffer).toString();
						mrCallBack.onMessageReceived(msg, key);
						buffer.clear();
					}
				}
			}
			
		}catch (Exception e) {
			e.printStackTrace();
			mrCallBack.onConnectionFailed();
			//getProcessManager().setNeedsRegistration(true);
		}

	}
	
	
}
